import pygame, sys, random, time, copy

#常量
from pygame.locals import *
UP, DOWN, LEFT, RIGHT = '向上', '向下', '向左', '向右'
#               R    G    B
PURPLE      = (255,   0, 255)
BLUE        = (  0,   0, 255)
LIGHTBLUE   = (170, 190, 255)
BROWN       = ( 85,  65,   0)
HIGHLIGHTCOLOR = PURPLE
BGCOLOR = LIGHTBLUE
GRIDCOLOR = BLUE
SCORECOLOR = BROWN
FPS = 30
WINDOWWIDTH = 800
WINDOWHEIGHT = 600
WINDOWCAPTION = '宝石三连消消乐'
FONTFILENAME = 'res/msyh.ttc'
FONTSIZE = 20
ICONFILENAME = 'res/gameicon.png'
SOUNDFILENAME = 'res/blockslide.mp3'

BOARDWIDTH = 12
BOARDHEIGHT = 9
GEMIMAGESIZE = 64
NUMGEMIMAGES = 7
ROWABOVEBOARD = '新宝石出生地，格板上面的一行'
XMARGIN = (WINDOWWIDTH - GEMIMAGESIZE * BOARDWIDTH) // 2
YMARGIN = (WINDOWHEIGHT - GEMIMAGESIZE * BOARDHEIGHT) // 2
EMPTY_SPACE = -1
MOVERATE = 25
#终止程序
def terminate():
    pygame.quit()
    sys.exit()
#关闭窗口或按Esc键退出程序
def checkForQuit():
    for event in pygame.event.get(QUIT):
        terminate()
    for event in pygame.event.get(KEYUP):
        if event.key == K_ESCAPE:
            terminate()
        else:
            pygame.event.post(event)
def getBlankBoard():
    board = []
    for x in range(BOARDWIDTH):
        board.append([])
        for y in range(BOARDHEIGHT):
            board[x].append(EMPTY_SPACE)
    return board
def drawBoard(board):
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            pygame.draw.rect(DISPLAYSURFACE, GRIDCOLOR, BOARDRECTS[x][y], 1)
            gemToDraw = board[x][y]
            if gemToDraw != EMPTY_SPACE:
                DISPLAYSURFACE.blit(GEMIMAGES[gemToDraw], BOARDRECTS[x][y])
def getDroppingGems(board):
    boardCopy = copy.deepcopy(board)
    droppingGems = []
    for x in range(BOARDWIDTH):
        #从下往上检查每一列，
        for y in range(BOARDHEIGHT-2, -1, -1):
            #如果下面那格是空的，而当前这格不是空的（有宝石），那么当前这格的宝石就是需要生成坠落动画的，记录在案
            if boardCopy[x][y+1] == EMPTY_SPACE and boardCopy[x][y] != EMPTY_SPACE:
                droppingGems.append({'imageNum':boardCopy[x][y], 'x':x, 'y':y, 'direction':DOWN})
                boardCopy[x][y] = EMPTY_SPACE#假如当前这格将坠落，那么就标记为空，以便判断它上面那格的宝石也将坠落。
    return droppingGems
def pullDownAllGems(board):
    for x in range(BOARDWIDTH):
        #登记此列所有宝石
        gemsInColumn = []
        for y in range(BOARDHEIGHT):
            if board[x][y] != EMPTY_SPACE:
                gemsInColumn.append(board[x][y])
        #被登记的宝石集中放在此列的下部，上面用空格填充
        board[x] = ([EMPTY_SPACE] * (BOARDHEIGHT - len(gemsInColumn))) + gemsInColumn
def getGemAt(board, x, y):
    if x < 0 or y < 0 or x >= BOARDWIDTH or y >= BOARDHEIGHT:
        return None
    else:
        return board[x][y]
def getDropSlots(board):
    boardCopy = copy.deepcopy(board)
    pullDownAllGems(boardCopy)
    dropSlots = list([] for i in range(BOARDWIDTH))
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT-1, -1, -1):
            if boardCopy[x][y] == EMPTY_SPACE:
                possibleGems = list(range(len(GEMIMAGES)))
                #随机生成的新宝石不应与其周围宝石一样
                for offsetX, offsetY in ((0,-1), (0,1), (-1,0), (1,0)):
                    neighborGem = getGemAt(boardCopy, x+offsetX, y+offsetY)
                    if neighborGem != None:
                        if neighborGem in possibleGems:
                            possibleGems.remove(neighborGem)
                newGem = random.choice(possibleGems)
                boardCopy[x][y] = newGem
                dropSlots[x].append(newGem)
    return dropSlots
def moveGems(board, movingGems):
    for gem in movingGems:
        if gem['y'] == ROWABOVEBOARD:
            board[gem['x']][0] = gem['imageNum']
        else:
            board[gem['x']][gem['y']] = EMPTY_SPACE
            movex, movey = 0, 0
            if gem['direction'] == UP:
                movey = -1
            elif gem['direction'] == DOWN:
                movey = 1
            elif gem['direction'] == LEFT:
                movex = -1
            elif gem['direction'] == RIGHT:
                movey = 1
                
            board[gem['x']+movex][gem['y']+movey] = gem['imageNum']
def drawMovingGem(gem, progress):
    basex, basey = gem['x'], gem['y']
    if basey == ROWABOVEBOARD:
        basey = -1
    pixelx = XMARGIN + (GEMIMAGESIZE * basex)
    pixely = YMARGIN + (GEMIMAGESIZE * basey)

    movex, movey =0, 0
    progress *= 0.01
    if gem['direction'] == UP:
        movey = - int(GEMIMAGESIZE * progress)
    elif gem['direction'] == DOWN:
        movey = + int(GEMIMAGESIZE * progress)
    elif gem['direction'] == LEFT:
        movex = - int(GEMIMAGESIZE * progress)
    elif gem['direction'] == RIGHT:
        movex = + int(GEMIMAGESIZE * progress)
        
    r = pygame.Rect((pixelx+movex, pixely+movey, GEMIMAGESIZE, GEMIMAGESIZE))
    DISPLAYSURFACE.blit(GEMIMAGES[gem['imageNum']], r)
def animateMovingGems(board, gems, pointsText, score):
    progress = 0
    while progress < 100:
        DISPLAYSURFACE.fill(BGCOLOR)
        drawBoard(board)
        for gem in gems:
            drawMovingGem(gem, progress)
        pygame.display.update()
        FPSCLOCK.tick(FPS)
        progress += MOVERATE
def getBoardCopyMinusGems(board, gems):
    boardCopy = copy.deepcopy(board)
    for gem in gems:
        if gem['y'] != ROWABOVEBOARD:
            boardCopy[gem['x']][gem['y']] = EMPTY_SPACE
    return boardCopy
def fillBoardAndAnimate(board, points, score):
    dropSlots = getDropSlots(board) #准备足以填满各列的宝石
    while dropSlots != [[]] * BOARDWIDTH:
        movingGems = getDroppingGems(board) #登记各列下方有空格的宝石(应该向下坠落)
        for x in range(len(dropSlots)):
            if len(dropSlots[x]) != 0:
                movingGems.append({'imageNum':dropSlots[x][0], 'x':x, 'y':ROWABOVEBOARD, 'direction':DOWN})
        #宝石坠落的动画
        boardCopy = getBoardCopyMinusGems(board, movingGems)
        animateMovingGems(boardCopy, movingGems, points, score)
        #宝石坠落动画后新的数据
        moveGems(board, movingGems)
        for x in range(len(dropSlots)):
            if len(dropSlots[x]) != 0:
                board[x][0] = dropSlots[x][0]
                del dropSlots[x][0]
def checkForGemClick(pos):
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            if BOARDRECTS[x][y].collidepoint(pos[0], pos[1]):
                return {'x':x, 'y':y} #找到被选中宝石的位置
    return None #未选中任何宝石
def findMatchingGems(board):
    gemsToRemove = []
    boardCopy = copy.deepcopy(board)
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            # 水平寻找>=3连匹配
            if getGemAt(boardCopy, x, y) != EMPTY_SPACE and \
               getGemAt(boardCopy, x, y) == getGemAt(boardCopy, x+1, y) == getGemAt(boardCopy, x+2, y):
                thisGem = getGemAt(boardCopy, x, y)
                offset = 0
                removeSet = []
                while thisGem == getGemAt(boardCopy, x+offset, y):
                    removeSet.append({'x':x+offset, 'y': y})
                    boardCopy[x+offset][y] = EMPTY_SPACE
                    offset += 1
                gemsToRemove.append(removeSet)
            # 垂直寻找>=3连匹配
            if getGemAt(boardCopy, x, y) != EMPTY_SPACE and \
               getGemAt(boardCopy, x, y) == getGemAt(boardCopy, x, y+1) == getGemAt(boardCopy, x, y+2):
                thisGem = getGemAt(boardCopy, x, y)
                offset = 0
                removeSet = []
                while thisGem == getGemAt(boardCopy, x, y+offset):
                    removeSet.append({'x':x, 'y':y+offset})
                    boardCopy[x][y+offset] = EMPTY_SPACE
                    offset += 1
                gemsToRemove.append(removeSet)
    return gemsToRemove
def isSwappable(board, gemA, gemB):
    #可交换条件：1、相邻；2、交换后能出现三连
    boardCopy = copy.deepcopy(board)
    if gemA['x'] == gemB['x'] and abs(gemA['y'] - gemB['y']) == 1 or \
       gemA['y'] == gemB['y'] and abs(gemA['x'] - gemB['x']) == 1:
        boardCopy[gemA['x']][gemA['y']], boardCopy[gemB['x']][gemB['y']] = boardCopy[gemB['x']][gemB['y']], boardCopy[gemA['x']][gemA['y']]
        if len(findMatchingGems(boardCopy)) > 0:
            return True
    return False
def swap(board, gemA, gemB):
    board[gemA['x']][gemA['y']], board[gemB['x']][gemB['y']] = board[gemB['x']][gemB['y']], board[gemA['x']][gemA['y']]
def drawScore(score):
    scoreSurface = BASICFONT.render(str(score), True, SCORECOLOR)
    scoreRect = scoreSurface.get_rect()
    scoreRect.bottomleft = (10, WINDOWHEIGHT-6)
    DISPLAYSURFACE.blit(scoreSurface, scoreRect)
def camMakeMove(board):
    动一下成三连的构型 = (((0,1), (1,0), (2,0)),
                         ((0,1), (1,1), (2,0)),
                         ((0,0), (1,1), (2,0)),
                         ((0,1), (1,0), (2,1)),
                         ((0,0), (1,0), (2,1)),
                         ((0,0), (1,1), (2,1)),
                         ((0,0), (0,2), (0,3)),
                         ((0,0), (0,1), (0,3)))
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            for pat in 动一下成三连的构型:
                if (getGemAt(board, x+pat[0][0], y+pat[0][1]) == \
                    getGemAt(board, x+pat[1][0], y+pat[1][1]) == \
                    getGemAt(board, x+pat[2][0], y+pat[2][1]) != None):
                    return[{'x':x+pat[0][0], 'y':y+pat[0][1]},
                           {'x':x+pat[1][0], 'y':y+pat[1][1]},
                           {'x':x+pat[2][0], 'y':y+pat[2][1]}]
                if (getGemAt(board, x+pat[0][1], y+pat[0][0]) == \
                    getGemAt(board, x+pat[1][1], y+pat[1][0]) == \
                    getGemAt(board, x+pat[2][1], y+pat[2][0]) != None):
                    return [{'x':x+pat[0][1], 'y':y+pat[0][0]},
                            {'x':x+pat[1][1], 'y':y+pat[1][0]},
                            {'x':x+pat[2][1], 'y':y+pat[2][0]}]
    return None
def highlightSpace(gems):
    for gem in gems:
        pygame.draw.rect(DISPLAYSURFACE, HIGHLIGHTCOLOR, BOARDRECTS[gem['x']][gem['y']], 4)
def runGame():
    gameBoard = getBlankBoard()
    score = 0
    fillBoardAndAnimate(gameBoard, [], score)
    firstSelectedGem, secondSelectedGem = None, None
    mouseDownPosition, mouseUpPosition = None, None
    helpMode = True
    gameIsOver = False
    while True:
        checkForQuit()#检查来自鼠标或键盘的退出事件，结束程序
        #在内存绘制
        DISPLAYSURFACE.fill (BGCOLOR)
        drawBoard(gameBoard)
        oneOffPatternGems = camMakeMove(gameBoard)
        if oneOffPatternGems != None:
            if helpMode: # 帮助模式下，把与三连模式仅差一个移动的那些宝石圈起来做个提醒
                highlightSpace(oneOffPatternGems)
        else:#找不到任何可移动的宝石，游戏结束
            gameIsOver = True
        #检测鼠标动作并翻译成选定两颗宝石的含义
        for event in pygame.event.get():
            if event.type == MOUSEBUTTONDOWN:#按下鼠标键，既是点击的开始，又是拖拽的开始
                mouseDownPosition = event.pos
            elif event.type == MOUSEBUTTONUP:
                mouseUpPosition = event.pos
            elif event.type == KEYUP:
                if event.key == K_h: #按h键打开或关闭帮助模式
                    helpMode = not helpMode
        if mouseDownPosition and mouseUpPosition: #已发生了鼠标DOWN&UP的动作周期
            if mouseUpPosition == mouseDownPosition:#同一点的DOWN&UP构成单击事件；
                if not firstSelectedGem:
                    firstSelectedGem = checkForGemClick(mouseUpPosition)
                elif not secondSelectedGem:
                    secondSelectedGem = checkForGemClick(mouseUpPosition)
            else:#位置不同，则构成了一次拖拽
                firstSelectedGem = checkForGemClick(mouseDownPosition)
                secondSelectedGem = checkForGemClick(mouseUpPosition)
            mouseDownPosition, mouseUpPosition = None, None
        
        #根据选定的宝石
        if firstSelectedGem and secondSelectedGem:
            if isSwappable(gameBoard, firstSelectedGem, secondSelectedGem):
                swap(gameBoard, firstSelectedGem, secondSelectedGem)
                matchedGems = findMatchingGems(gameBoard)
                while matchedGems != []:
                    for gemSet in matchedGems:
                        for gem in gemSet:
                            gameBoard[gem['x']][gem['y']] = EMPTY_SPACE
                            score += 1
                    BASICSOUND.play()
                    fillBoardAndAnimate(gameBoard, [], score)
                    matchedGems = findMatchingGems(gameBoard)
            firstSelectedGem, secondSelectedGem = None, None
        if gameIsOver:
            return
        drawScore(score)
        #在内存绘制的所有内容更新到屏幕
        pygame.display.update()
        FPSCLOCK.tick(FPS)    
    
def gemgem():
    #初始化    
    global DISPLAYSURFACE, FPSCLOCK, BASICFONT, BASICSOUND, GEMIMAGES, BOARDRECTS
    pygame.init()
    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURFACE = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32)
    pygame.display.set_caption(WINDOWCAPTION)
    pygame.display.set_icon(pygame.image.load(ICONFILENAME))    
    BASICFONT = pygame.font.Font(FONTFILENAME, FONTSIZE)
    BASICSOUND = pygame.mixer.Sound(SOUNDFILENAME)
    GEMIMAGES = []
    for i in range(1, NUMGEMIMAGES+1):
        gemImage = pygame.image.load('res/gem%s.png' % i)
        gemImage = pygame.transform.smoothscale(gemImage, (GEMIMAGESIZE, GEMIMAGESIZE))
        GEMIMAGES.append(gemImage)
    BOARDRECTS = []
    for x in range(BOARDWIDTH):
        BOARDRECTS.append([])
        for y in range(BOARDHEIGHT):
            r = pygame.Rect((XMARGIN + (x * GEMIMAGESIZE), YMARGIN + (y * GEMIMAGESIZE), GEMIMAGESIZE, GEMIMAGESIZE))
            BOARDRECTS[x].append(r)
    while True:
        runGame()
        
        
        
if __name__ == '__main__':
    gemgem()